﻿//
// Copyright 2024 Exhunter Technology Co.Ltd2.
//

using System.Collections.Generic;
using System;
using System.Net.NetworkInformation;
using System.Security.Cryptography;
using System.Threading;
using X18.Framework.Core.Hash;

namespace X18.Framework.Core.Sequential
{
    /// <summary>
    /// 生成顺序的GUID
    /// 参考 https://www.rfc-editor.org/rfc/rfc4122#section-4.1.3
    /// </summary>
    public static class SequentialGuid
    {
        /// <summary>
        /// 是否支持 SequentialGuid;
        /// </summary>
        private static readonly bool SupportSequentialGuid;
        /// <summary>
        /// 时间戳、序列同步锁
        /// </summary>
        private static readonly object SyncLock;
        /// <summary>
        /// 节点数据长度(字节)
        /// </summary>
        private static readonly int NodeLength = 6;
        /// <summary>
        /// 节点数据
        /// </summary>
        private static readonly byte[] Node;
        /// <summary>
        /// 开始时间 1582-10-15 0:0:0
        /// </summary>
        private static ulong T15821015;
        /// <summary>
        /// 时间戳
        /// </summary>
        private static ulong LastTicks;
        /// <summary>
        /// 序列号
        /// </summary>
        private static ulong Sequence;
        /// <summary>
        /// 时钟序列号数据长度(字节)
        /// </summary>
        private static readonly int ClockSequenceLength = 2;
        /// <summary>
        /// 时钟序列号
        /// </summary>
        private static byte[] ClockSequence;

        static SequentialGuid()
        {
            SyncLock = new object();
            lock (SyncLock)
            {
                Node = GetMacAddressBytes(out SupportSequentialGuid);
                T15821015 = (ulong)new DateTime(1582, 10, 15, 0, 0, 0).Ticks + 10000000;
                LastTicks = (ulong)DateTime.Now.Ticks - T15821015;
                Sequence = 0;
                ClockSequence = InitClockSequence();
            }
        }

        private static byte[] InitClockSequence()
        {
            Random random = new Random();
            int clockSequence = random.Next(1, 0xffff) & 0xffff;
            ClockSequence = new byte[ClockSequenceLength];
            ClockSequence[0] = (byte)(clockSequence & 0xff);
            ClockSequence[1] = (byte)(clockSequence >> 8 & 0xff);
            return ClockSequence;
        }

        /// <summary>
        /// 时钟序列号
        /// </summary>
        public static void SetClockSequence(int clockSequence)
        {
            lock (SyncLock)
            {
                ClockSequence = new byte[ClockSequenceLength];
                ClockSequence[0] = (byte)(clockSequence & 0xff);
                ClockSequence[1] = (byte)(clockSequence >> 8 & 0xff);
            }
        }

        private static byte[] GetMacAddressBytes(out bool isok)
        {
            isok = false;
            byte[] result = new byte[NodeLength];
            //
            NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
            List<NetworkInterface> eths = new List<NetworkInterface>();
            for (int i = 0; i < nics.Length; i++)
            {
                NetworkInterface nic = nics[i];
                if (nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet)
                {
                    eths.Add(nic);
                }
            }
            //
            byte[] buffer = new byte[nics.Length * NodeLength];
            nics = eths.ToArray();
            for (int i = 0; i < nics.Length; i++)
            {
                NetworkInterface nic = nics[i];
                if (nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet)
                {
                    // 获取物理地址(MAC地址)
                    PhysicalAddress physicalAddress = nic.GetPhysicalAddress();
                    result = physicalAddress.GetAddressBytes();
                    // 复制到缓存中
                    Array.Copy(physicalAddress.GetAddressBytes(), 0, buffer, i * NodeLength, NodeLength);
                    // 设置支持 SequentialGuid
                    isok = true;
                }
            }
            // 计算Hash值
            byte[] hash = Sha1.ComputeHashToBytes(buffer);
            // 返回高位字节
            Array.Copy(hash, 0, result, 0, NodeLength);
            return result;
        }

        public static Guid NewSequentialGuid()
        {
            if (SupportSequentialGuid)
            {
                ulong timestamp = 0L;
                lock (SyncLock)
                {
                    // 如果时间反转
                    ulong nowTicks = (ulong)DateTime.Now.Ticks - T15821015;
                    while (nowTicks - LastTicks < 0)
                    {
                        nowTicks = (ulong)DateTime.Now.Ticks - T15821015;
                        Thread.Sleep(1);
                    }
                    // 
                    if (nowTicks - LastTicks > 100000000)
                    {
                        LastTicks = nowTicks;
                        Sequence = 0;
                    }
                    Sequence += 1;
                    timestamp = LastTicks + Sequence;
                }
                // Return
                byte[] result = new byte[16];
                // Timestamp
                byte[] bytes = System.BitConverter.GetBytes(timestamp);
                result[8] = bytes[7];
                result[9] = bytes[6];
                result[10] = bytes[5];
                result[11] = bytes[4];
                result[12] = bytes[3];
                result[13] = bytes[2];
                result[14] = bytes[1];
                result[15] = bytes[0];
                // Version
                result[8] = (byte)(result[8] & 0x0f | 0x10);
                // Clock Sequence
                result[7] = ClockSequence[1];
                result[6] = ClockSequence[0];
                result[7] = (byte)(result[7] & 0x3f | 0x80);
                // Node
                result[0] = Node[3];
                result[1] = Node[2];
                result[2] = Node[1];
                result[3] = Node[0];
                result[4] = Node[5];
                result[5] = Node[4];
                return new Guid(result);
            }
            else
            {
                return Guid.NewGuid();
            }
        }

        public static Guid NewGuid()
        {
            return Guid.NewGuid();
        }
    }
}
